//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Text;
using System.Collections.Generic;
using EffiProz.Core.DataTypes;
using EffiProz.Core.Lib;

namespace EffiProz.Core
{


    // fredt@users 20020130 - patch 491987 by jimbag@users
    // fredt@users 20020320 - doc 1.7.0 - update
    // fredt@users 20020401 - patch 442993 by fredt - arithmetic expressions
    // to allow mixed type arithmetic expressions beginning with a narrower type
    // changes applied to several lines of code and not marked separately
    // consists of changes to arithmatic functions to allow promotion of
    // java.lang.Number values and new functions to choose type for promotion
    // fredt@users 20020401 - patch 455757 by galena@users (Michiel de Roo)
    // interpretation of TINYINT as Byte instead of Short
    // fredt@users 20020130 - patch 491987 by jimbag@users
    // support for sql standard char and varchar. size is maintained as
    // defined _in the DDL and trimming and padding takes place accordingly
    // modified by fredt - trimming and padding are turned off by default but
    // can be applied accross the database by defining sql.enforce_size=true in
    // database.properties file
    // fredt@users 20020215 - patch 1.7.0 by fredt - quoted identifiers
    // applied to different parts to support the sql standard for
    // naming of columns and tables (use of quoted identifiers as names)
    // fredt@users 20020328 - patch 1.7.0 by fredt - change REAL to Double
    // fredt@users 20020402 - patch 1.7.0 by fredt - type conversions
    // frequently used type conversions are done without creating temporary
    // Strings to reduce execution time and garbage collection
    // fredt@users 20021013 - patch 1.7.1 by fredt - type conversions
    // scripting of Double.Nan and infinity values
    // fredt@users 20030715 - patch 1.7.2 - type narrowing for numeric values
    // fredt@users - patch 1.8.0 - enforcement of precision and scale

    /**
     *  Implementation of SQL table columns as defined _in DDL statements with
     *  static methods to process their values.<p>
     *
     *  Enhanced type checking and conversion by fredt@users
     *
     * @author Thomas Mueller (Hypersonic SQL Group)
     * @author fredt@users
     * @version    1.8.0
     * @since Hypersonic SQL
     */
    public class Column
    {

        // --------------------------------------------------
        // DDL name, size, scale, null, identity and default values
        // most variables are const but not declared so because of a bug in
        // JDK 1.1.8 compiler
        public QNameManager.QName columnName;
        private int colType;
        private int colSize;
        private int colScale;
        private bool _isNullable;
        private bool _isIdentity;
        private bool _isPrimaryKey;
        private Expression defaultExpression;
        public long identityStart;
        public long identityIncrement;
        public static long MAX_LONG = (long.MaxValue);
        public static long MIN_LONG = (long.MinValue);
        public static long MAX_INT = (int.MaxValue);
        public static long MIN_INT = (int.MinValue);
        public static Decimal BIG_DECIMAL_0 = Decimal.Zero;
        public static Decimal BIG_DECIMAL_1 = Decimal.One;

        /**
         *  Creates a column defined _in DDL statement.
         *
         * @param  name
         * @param  nullable
         * @param  type
         * @param  size
         * @param  scale
         * @param  identity
         * @param  startvalue
         * @param  increment
         * @param  primarykey
         * @param  defstring
         */
        public Column(QNameManager.QName name, bool nullable, int type, int size, int scale,
                bool primarykey,
                Expression defexpression)
        {

            columnName = name;
            _isNullable = nullable;
            colType = type;
            colSize = size;
            colScale = scale;
            _isPrimaryKey = primarykey;
            defaultExpression = defexpression;
        }

        public void setIdentity(bool identity, long startvalue,
                         long increment)
        {

            _isIdentity = identity;
            identityStart = startvalue;
            identityIncrement = increment;

            if (_isIdentity)
            {
                if (colType == Types.INTEGER)
                {
                    if (identityStart > int.MaxValue
                            || identityIncrement > int.MaxValue)
                    {
                        throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE,
                                          columnName.statementName);
                    }
                }
            }
        }

        private Column() { }

        /**
         * Used for primary key changes.
         */
        public Column duplicate(bool withIdentity)
        {

            Column newCol = new Column();

            newCol.columnName = columnName;
            newCol._isNullable = _isNullable;
            newCol.colType = colType;
            newCol.colSize = colSize;
            newCol.colScale = colScale;
            newCol.defaultExpression = defaultExpression;

            if (withIdentity)
            {
                newCol.setIdentity(_isIdentity, identityStart, identityIncrement);
            }

            return newCol;
        }

        public void setType(Column other)
        {

            _isNullable = other._isNullable;
            colType = other.colType;
            colSize = other.colSize;
            colScale = other.colScale;
        }

        /**
         *  Is this the identity column _in the table.
         *
         * @return bool
         */
        public bool isIdentity()
        {
            return _isIdentity;
        }

        /**
         *  Is column nullable.
         *
         * @return bool
         */
        public bool isNullable()
        {
            return _isNullable;
        }

        /**
         *  Set nullable.
         *
         */
        public void setNullable(bool value)
        {
            _isNullable = value;
        }

        /**
         *  Is this single column primary key of the table.
         *
         * @return bool
         */
        public bool isPrimaryKey()
        {
            return _isPrimaryKey;
        }

        /**
         *  Set primary key.
         *
         */
        public void setPrimaryKey(bool value)
        {
            _isPrimaryKey = value;
        }

        /**
         *  Returns default value _in the session context.
         */
        public Object getDefaultValue(Session session)
        {

            return defaultExpression == null ? null
                                             : defaultExpression.getValue(session,
                                             colType);
        }

        /**
         *  Returns DDL for default value.
         */
        public string getDefaultDDL()
        {

            string ddl = null;

            try
            {
                ddl = defaultExpression == null ? null
                                                : defaultExpression.getDDL();
            }
            catch (CoreException) { }

            return ddl;
        }

        /**
         *  Returns default expression for the column.
         */
        public Expression getDefaultExpression()
        {
            return defaultExpression;
        }

        public void setDefaultExpression(Expression expr)
        {
            defaultExpression = expr;
        }

        /**
         *  Type of the column.
         *
         * @return java.sql.Types int value for the column
         */
        public int getType()
        {
            return colType;
        }

        public int getDIType()
        {
            return colType == Types.VARCHAR_IGNORECASE ? Types.VARCHAR
                                                       : colType;
        }

        public int getDITypeSub()
        {

            if (colType == Types.VARCHAR_IGNORECASE)
            {
                return Types.TYPE_SUB_IGNORECASE;
            }

            return Types.TYPE_SUB_DEFAULT;
        }

        /**
         *  Size of the column _in DDL (0 if not defined).
         *
         * @return DDL size of column
         */
        public int getSize()
        {
            return colSize;
        }

        /**
         *  Scale of the column _in DDL (0 if not defined).
         *
         * @return DDL scale of column
         */
        public int getScale()
        {
            return colScale;
        }

        /**
         *  Add two object of a given type
         *
         * @param  a
         * @param  b
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object add(Object a, Object b, int type)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (type)
            {

                case Types.NULL:
                    return null;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = ((double)a);
                        double bd = ((double)b);

                        return ad + bd;

                        //                return new Double(ad + bd);
                    }
                case Types.VARCHAR:
                case Types.CHAR:
                case Types.LONGVARCHAR:
                case Types.VARCHAR_IGNORECASE:
                    return (String)a + (String)b;

                case Types.NUMERIC:
                case Types.DECIMAL:
                    {
                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return abd + bbd;
                    }
                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    {
                        int ai = ((int)a);
                        int bi = ((int)b);

                        return ai + bi;
                    }
                case Types.BIGINT:
                    {
                        long longa = ((long)a);
                        long longb = ((long)b);

                        return longa + longb;
                    }
                default:
                    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED,
                                      Types.getTypeString(type));
            }
        }

        /**
         *  Concat two objects by turning them into strings first.
         *
         * @param  a
         * @param  b
         * @return result
         * @throws  HsqlException
         */
        public static Object concat(Object a, Object b)
        {

            if (a == null || b == null)
            {
                return null;
            }

            return a.ToString() + b.ToString();
        }

        /**
         *  Negate a numeric object.
         *
         * @param  a
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object negate(Object a, int type)
        {

            if (a == null)
            {
                return null;
            }

            switch (type)
            {

                case Types.NULL:
                    return null;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = -((double)a);

                        return ad;
                    }
                case Types.NUMERIC:
                case Types.DECIMAL:
                    return -((Decimal)a);

                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    return -((int)a);

                case Types.BIGINT:
                    return -((long)a);

                default:
                    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED,
                                      Types.getTypeString(type));
            }
        }

        /**
         *  Multiply two numeric objects.
         *
         * @param  a
         * @param  b
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object multiply(Object a, Object b,
                               int type)
        {

            if (a == null || b == null)
            {
                return null;
            }

            // fredt@users - type conversion - may need to apply to other arithmetic operations too
            if (!(a is int && b is int))
            { //TODO
                a = Column.convertObject(a, type);
                b = Column.convertObject(b, type);
            }

            switch (type)
            {

                case Types.NULL:
                    return null;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = ((double)a);
                        double bd = ((double)b);

                        return ad * bd;
                    }
                case Types.NUMERIC:
                case Types.DECIMAL:
                    {
                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return abd * bbd;
                    }
                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    {
                        int ai = ((int)a);
                        int bi = ((int)b);

                        return (ai * bi);
                    }
                case Types.BIGINT:
                    {
                        long longa = ((long)a);
                        long longb = ((long)b);

                        return (longa * longb);
                    }
                default:
                    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED,
                                      Types.getTypeString(type));
            }
        }

        /**
         *  Divide numeric object a by object b.
         *
         * @param  a
         * @param  b
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object divide(Object a, Object b, int type)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (type)
            {

                case Types.NULL:
                    return null;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = ((double)a);
                        double bd = ((double)b);


                        if (bd == 0)
                        {
                            throw Trace.error(Trace.DIVISION_BY_ZERO);
                        }

                        return (ad / bd);
                    }
                case Types.NUMERIC:
                case Types.DECIMAL:
                    {
                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        if (bbd == 0)
                        {
                            throw Trace.error(Trace.DIVISION_BY_ZERO);
                        }

                
                        return abd / bbd; 
                    }
                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    {
                        int ai = ((int)a);
                        int bi = ((int)b);

                        if (bi == 0)
                        {
                            throw Trace.error(Trace.DIVISION_BY_ZERO);
                        }

                        return (ai / bi);
                    }
                case Types.BIGINT:
                    {
                        long longa = ((long)a);
                        long longb = ((long)b);

                        return (longb == 0) ? 0
                                            : (longa / longb);
                    }
                default:
                    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED,
                                      Types.getTypeString(type));
            }
        }

        /**
         *  Subtract numeric object b from object a.
         *
         * @param  a
         * @param  b
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object subtract(Object a, Object b,
                               int type)
        {

            if (a == null || b == null)
            {
                return null;
            }

            switch (type)
            {

                case Types.NULL:
                    return null;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = ((double)a);
                        double bd = ((double)b);

                        return (ad - bd);
                    }
                case Types.NUMERIC:
                case Types.DECIMAL:
                    {
                        Decimal abd = (Decimal)a;
                        Decimal bbd = (Decimal)b;

                        return abd - (bbd);
                    }
                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    {
                        int ai = ((int)a);
                        int bi = ((int)b);

                        return (ai - bi);
                    }
                case Types.BIGINT:
                    {
                        long longa = ((long)a);
                        long longb = ((long)b);

                        return (longa - longb);
                    }
                default:
                    throw Trace.error(Trace.FUNCTION_NOT_SUPPORTED,
                                      Types.getTypeString(type));
            }
        }

        // boucherb@users 2003-09-25
        // TODO:  Maybe use //#ifdef tag or reflective static method attribute
        // instantiation to take advantage of String.CompareToIgnoreCase when
        // available (JDK 1.2 and greater) during ANT build. That or perhaps
        // consider using either local character-wise comparison or first converting
        // to lower case and then to upper case. Sun states that the JDK 1.2 introduced
        // String.CompareToIngnorCase() comparison involves calling
        // Char.ToLower(Char.ToUpper()) on compared characters,
        // to correctly handle some caveats concering using only the one operation or
        // the other outside the ascii character range.
        // fredt@users 20020130 - patch 418022 by deforest@users
        // use of rtrim() to mimic SQL92 behaviour

        /**
         *  Compare a with b and return int value as result.
         *
         * @param  a instance of Java wrapper, depending on type, but always same for a & b (can be null)
         * @param  b instance of Java wrapper, depending on type, but always same for a & b (can be null)
         * @param  type one of the java.sql.Types
         * @return result 1 if a>b, 0 if a=b, -1 if b>a
         * @throws  HsqlException
         */
        public static int compare(Collation collation, Object a, Object b, int type)
        {

            int i = 0;

            if (a == b)
            {
                return 0;
            }

            // Current null handling here: null==null and smaller any value
            // Note, standard SQL null handling is handled by Expression.test() calling testNull() instead of this!
            // Attention, this is also used for grouping ('null' is one group)
            if (a == null)
            {
                return -1;
            }

            if (b == null)
            {
                return 1;
            }

        

            switch (type)
            {

                case Types.NULL:
                    return 0;

                case Types.VARCHAR:
                case Types.LONGVARCHAR:
                    return collation.compare((String)a, (String)b);

                case Types.CHAR:
                    return collation.compare(Library.rtrim((String)a),
                                             Library.rtrim((String)b));

                case Types.VARCHAR_IGNORECASE:
                    return collation.compareIgnoreCase(((String)a),
                                                       ((String)b));

                case Types.TINYINT:
                case Types.SMALLINT:
                case Types.INTEGER:
                    {
                        int ai = Convert.ToInt32(a);
                        int bi = Convert.ToInt32(b);

                        return (ai > bi) ? 1
                                         : (bi > ai ? -1
                                                    : 0);
                    }
                case Types.BIGINT:
                    {
                        long longa = Convert.ToInt64(a);
                        long longb = Convert.ToInt64(b);

                        return (longa > longb) ? 1
                                               : (longb > longa ? -1
                                                                : 0);
                    }
                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    {
                        double ad = Convert.ToDouble(a);
                        double bd = Convert.ToDouble(b);

                        return (ad > bd) ? 1
                                         : (bd > ad ? -1
                                                    : 0);
                    }
                case Types.NUMERIC:
                case Types.DECIMAL:
                    {
                        decimal dcia = Convert.ToDecimal(a);
                        decimal dcib = Convert.ToDecimal(b);

                        return (dcia > dcib) ? 1
                                         : (dcib > dcia ? -1
                                                    : 0);
                    }
               
                case Types.DATE:
                    return DateTime.Compare((DateTime)a, (DateTime)b);

                case Types.TIME:
                    return DateTime.Compare((DateTime)a, (DateTime)b);

                case Types.TIMESTAMP:
                    return DateTime.Compare((DateTime)a, (DateTime)b);

                case Types.BOOLEAN:
                    {
                        bool boola = ((bool)a);
                        bool boolb = ((bool)b);

                        return (boola == boolb) ? 0
                                                : (boolb ? -1
                                                         : 1);
                    }
                case Types.BINARY:
                case Types.VARBINARY:
                case Types.LONGVARBINARY:
                    if (a is Binary && b is Binary)
                    {
                        i = CompareTo(((Binary)a).getBytes(),
                                      ((Binary)b).getBytes());
                    }
                    break;

                case Types.OTHER:
                    return 0;
            }

            return (i == 0) ? 0
                            : (i < 0 ? -1
                                     : 1);
        }

        /**
         *  Convert an object into a Java object that represents its SQL type.<p>
         *  All internal type conversion operations start with
         *  this method. If a direct conversion doesn't take place, the object
         *  is converted into a string first and an attempt is made to convert
         *  the string into the target type.<br>
         *
         *  One objective of this mehod is to ensure the Object can be converted
         *  to the given SQL type. For example, a very large BIGINT
         *  value cannot be narrowed down to an INTEGER or SMALLINT.<br>
         *
         *  Type conversion performed by this method has gradually evolved _in 1.7.2
         *  to allow narrowing of numeric types _in all cases according to the SQL
         *  standard.<br>
         *
         *  Another new objective is to normalize DATETIME values.<br>
         *
         *  Objects set via JDBC PreparedStatement use this method to convert
         *  the data to the Java type that is required for custom serialization
         *  by the engine. <br>
         *
         * @param  o
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static Object convertObject(Object o,
                                           int type)
        {

            try
            {
                if (o == null)
                {
                    return null;
                }

                switch (type)
                {

                    case Types.NULL:
                        return null;

                    case Types.TINYINT:
                        if (o is string)
                        {
                            o = ((String)o).Trim();
                            int val = int.Parse((String)o);
                            o = val;
                        }

                        if (o is int)
                        {
                            int temp = ((int)o);

                            if (byte.MaxValue < temp || temp < byte.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return o;
                        }
                        else if (o is long)
                        {
                            long temp = ((long)o);

                            if (byte.MaxValue < temp || temp < byte.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return (int)temp;
                        }
                        // fredt@users - direct conversion to optimise JDBC setObject(Byte)
                        else if (o is byte)
                        {
                            return (int)(byte)o;
                        }
                        else if (o is short)
                        {
                            int temp = ((short)o);

                            if (byte.MaxValue < temp || temp < byte.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return (int)temp;
                        }
                        // fredt@users - returns to this method for range checking
                        else if (o is Decimal)
                        {
                            return convertObject(convertToInt(o), type);
                        }
                        else if (o is bool)
                        {
                            return (int)(((bool)o) ? 1 : 0);
                        }
                        else
                        {
                            return Convert.ToInt32(o);
                        }
                       
        

                    case Types.SMALLINT:
                        if (o is string)
                        {
                            o =((String)o).Trim();

                            int val = int.Parse((String)o);

                            o = (val);
                        }

                        if (o is int)
                        {
                            int temp = ((int)o);

                            if (short.MaxValue < temp || temp < short.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return o;
                        }
                        else if (o is long)
                        {
                            long temp = ((long)o);

                            if (short.MaxValue < temp || temp < short.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return (int)temp;
                        }
                        // fredt@users - direct conversion for JDBC setObject(Short), etc.
                        else  if (o is byte) 
                        {
                            return (int)(byte)o;
                        }
                        else if (o is short)
                        {
                            return (int)(short)o;
                        }
                        // fredt@users - returns to this method for range checking
                        else  if (o is Decimal)
                        {
                            return convertObject(convertToInt(o), type);
                        }
                        else if (o is bool)
                        {
                            return  (int) (((bool)o) ? 1  : 0);
                        }
                        else
                        {
                            return Convert.ToInt32(o);
                        }
                       

                    case Types.INTEGER:
                        if (o is int)
                        {
                            return o;
                        }
                        else if (o is string)
                        {
                            o = ((String)o).Trim();
                            int val = int.Parse((String)o);
                            return val;
                        }
                        else if (o is long)
                        {
                            long temp = ((long)o);

                            if (int.MaxValue < temp || temp < int.MinValue)
                            {
                                throw Trace.error(
                                    Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                            }

                            return (int)temp;
                        }
                        else if (o is byte) 
                        {
                            return (int)(byte)o;
                        }
                        else if (o is short)
                        {
                            return (int)(short)o;
                        }
                        else if (o is Decimal)
                        {
                            return convertToInt(o);
                        }
                        else if (o is bool)
                        {
                            return (int)(((bool)o) ? 1 : 0);
                        }
                        else
                        {
                            return Convert.ToInt32(o);
                        }
         

                    case Types.BIGINT:
                        if (o is long)
                        {
                            return o;
                        }
                        else if (o is string)
                        {
                            o = ((String)o).Trim();

                            long val = long.Parse((String)o);

                            return val;
                        }
                        else if (o is int)
                        {
                            return (long)(int)o;
                        }
                        else if (o is Byte)
                        {
                            return (long)(byte)o;
                        }
                        else if (o is short)
                        {
                            return (long)(short)o;
                        }
                        else if (o is Decimal)
                        {
                            return convertToLong(o);
                        }
                        else if (o is bool)
                        {
                            return (long)(((bool)o) ? 1 : 0);
                        }
                        else
                        {
                            return Convert.ToInt64(o);
                        }
                     

                    case Types.REAL:
                    case Types.FLOAT:
                    case Types.DOUBLE:
                        if (o is long)
                        {
                            return (double)(long)o;
                        }
                        else if (o is string)
                        {
                            o = ((String)o).Trim();
                            double d = Double.Parse((String)o);              
                            return d;
                        }
                        else if (o is Decimal)
                        {
                            return (double)(decimal)o;
                        }
                        else if (o is bool)
                        {
                            return (double)(((bool)o) ? 1 : 0);
                        }
                        else
                        {
                            return Convert.ToDouble(o);
                        }                  

                    case Types.NUMERIC:
                    case Types.DECIMAL:
                        if (o is Decimal)
                        {
                            return o;
                        }
                        else if (o is string)
                        {
                            o = ((String)o).Trim();
                            decimal d = decimal.Parse((String)o);
                            return d;
                        }             
                        else if (o is bool)
                        {
                            return ((bool)o) ? BIG_DECIMAL_1 : BIG_DECIMAL_0;
                        }
                        else 
                        {
                            return Convert.ToDecimal(o);
                        }

 
                    case Types.BOOLEAN:
                        if (o is bool)
                        {
                            return o;
                        }
                        else if (o is string)
                        {
                            o =((String)o).Trim();

                            return ((String)o).Equals("TRUE", StringComparison.InvariantCultureIgnoreCase)
                                   ? true
                                   : false;
                        }
                        else if (o is byte)
                        {
                            return ((byte)o) == 0 ? false : true;
                        }
                        else if (o is short)
                        {
                            return ((short)o) == 0 ? false : true;
                        }

                        else if (o is int)
                        {
                            return  ((int)o) == 0 ? false  : true;
                        }

                        else if (o is long)
                        {
                            return ((long)o) == 0L ? false : true;
                        }

                        else if (o is double)
                        {
                            return ((Double)o) == 0.0  ? false : true;
                        }

                        else if (o is Decimal)
                        {
                            return ((Decimal)o).Equals(BIG_DECIMAL_0)  ? false  : true;
                        }
                        else
                        {
                            return Convert.ToBoolean(o);
                        }

                        throw Trace.error(Trace.WRONG_DATA_TYPE);
                    case Types.VARCHAR_IGNORECASE:
                    case Types.VARCHAR:
                    case Types.CHAR:
                    case Types.LONGVARCHAR:
                        if (o is string)
                        {
                            return o;
                        }
                        else if (o is DateTime)
                        {
                            return ((DateTime)o).ToString("o"); 
                        }

                        else if (o is byte[])
                        {
                            return StringConverter.byteToHex((byte[])o);
                        }
                        else
                        {
                            Convert.ToString(o);
                        }
                        break;

                    case Types.TIME:
                        if (o is DateTime)
                        {
                            return o;
                        }
                        else if (o is String)
                        {
                            return DateTime.Parse((String)o);
                        }
                        else
                        {
                            Convert.ToDateTime(o);
                        }
                  
                        break;

                    case Types.TIMESTAMP:
                       if (o is DateTime)
                        {
                            return o;
                        }
                        else if (o is String)
                        {
                            return DateTime.Parse((String)o);
                        }
                        else
                        {
                            Convert.ToDateTime(o);
                        }
                  
                        break;                      

                    case Types.DATE:
                        if (o is DateTime)
                        {
                            return o;
                        }
                        else if (o is String)
                        {
                            return DateTime.Parse((String)o);
                        }
                        else
                        {
                            Convert.ToDateTime(o);
                        }       

                        break;

                    case Types.BINARY:
                    case Types.VARBINARY:
                    case Types.LONGVARBINARY:
                        if (o is Binary)
                        {
                            return o;
                        }
                        else if (o is byte[])
                        {
                            return new Binary((byte[])o, false);
                        }
                        else if (o is String)
                        {

                            /**
                             * @todo fredt - we need this for script processing only
                             *  handle the script separately and process normal
                             *  conversion according to rules _in SQL
                             *  standard
                             */
                            return new Binary(
                                StringConverter.hexToByte((String)o), false);
                        }

                        throw Trace.error(Trace.INVALID_CONVERSION,
                                          Types.getTypeString(type));

                    // fredt@users 20030708 -  patch 1.7.2 - OBJECT handling - superseded
                    case Types.OTHER:
                        if (o is DotNetObject)
                        {
                            return o;
                        }
                        else if (o is String)
                        {

                            /**
                             * @todo fredt - we need this for script processing only
                             *  handle the script separately and allow normal Sting
                             *  objects to be stored as JavaObject
                             */
                            return new DotNetObject(
                                StringConverter.hexToByte((String)o));
                        }
                        else if (o is Binary)
                        {
                            return new DotNetObject(((Binary)o).getBytes());
                        }

                        return new DotNetObject(o);

                    default:
                        break;
                }

                if (o is DotNetObject)
                {
                    o = ((DotNetObject)o).getObject();

                    return convertObject(o, type);
                }

                return convertString(o.ToString(), type);
            }
            catch (CoreException e)
            {
                throw e;
            }
            catch (Exception e)
            {
                throw Trace.error(Trace.WRONG_DATA_TYPE, e.ToString());
            }
        }

        /**
         *  Return a java object based on a SQL string. This is called from
         *  convertObject(Object o, int type).
         *
         * @param  s
         * @param  type
         * @return
         * @throws  HsqlException
         */
        private static Object convertString(string s,
                                            int type)
        {

            switch (type)
            {

                case Types.TINYINT:
                case Types.SMALLINT:

                    // fredt - do maximumm / minimum checks on each type
                    return convertObject(s, type);

                case Types.INTEGER:
                    int val = int.Parse(s);

                    return (val);

                case Types.BIGINT:
                    return (long.Parse(s));

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    double d = double.Parse(s);                   

                    return d;

                case Types.VARCHAR_IGNORECASE:
                case Types.VARCHAR:
                case Types.CHAR:
                case Types.LONGVARCHAR:
                    return s;

                case Types.DATE:
                    return DateTime.Parse(s);

                case Types.TIME:
                    return DateTime.Parse(s);

                case Types.TIMESTAMP:
                    return DateTime.Parse(s);

                case Types.NUMERIC:
                case Types.DECIMAL:
                    s = Library.trim(s, " ", true, true);

                    return Decimal.Parse(s);

                case Types.BOOLEAN:
                    return s.Equals("TRUE", StringComparison.InvariantCultureIgnoreCase) ? true
                                                      : false;

                case Types.BINARY:
                case Types.VARBINARY:
                case Types.LONGVARBINARY:
                case Types.OTHER:
                default:
                    throw Trace.error(Trace.INVALID_CONVERSION,
                                      Types.getTypeString(type));
            }
        }

        /**
         *  Return an SQL representation of an object. Strings will be quoted
         *  with single quotes, other objects will represented as _in a SQL
         *  statement.
         *
         * @param  o
         * @param  type
         * @return result
         * @throws  HsqlException
         */
        public static string createSQLString(Object o, int type)
        {

            if (o == null)
            {
                return "NULL";
            }

            switch (type)
            {

                case Types.NULL:
                    return "NULL";

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    return createSQLString(((double)o));

                case Types.DATE:
                case Types.TIME:
                case Types.TIMESTAMP:
                    return StringConverter.toQuotedString(o.ToString(), '\'',
                                                          false);

                case Types.BINARY:
                case Types.VARBINARY:
                case Types.LONGVARBINARY:
                    if (!(o is Binary))
                    {
                        throw Trace.error(Trace.INVALID_CONVERSION);
                    }

                    return StringConverter.toQuotedString(
                        StringConverter.byteToHex(((Binary)o).getBytes()), '\'',
                        false);

                case Types.OTHER:
                    if (!(o is DotNetObject))
                    {
                        throw Trace.error(Trace.SERIALIZATION_FAILURE);
                    }

                    return StringConverter.toQuotedString(
                        StringConverter.byteToHex(((DotNetObject)o).getBytes()),
                        '\'', false);

                case Types.VARCHAR_IGNORECASE:
                case Types.VARCHAR:
                case Types.CHAR:
                case Types.LONGVARCHAR:
                    return createSQLString((String)o);

                default:
                    return o.ToString();
            }
        }

        public static string createSQLString(double x)
        {

            if (x == Double.NegativeInfinity)
            {
                return "-1E0/0";
            }

            if (x == Double.PositiveInfinity)
            {
                return "1E0/0";
            }

            if (Double.IsNaN(x))
            {
                return "0E0/0E0";
            }

            string s = (x.ToString());

      //       ensure the engine treats the value as a DOUBLE, not DECIMAL
            if (s.IndexOf('E') < 0)
            {
                s = s + ("E0");
            }           
            return s;
        }

        /**
         *  Turns a java string into a quoted SQL string
         *
         * @param  s java string
         * @return quoted SQL string
         */
        public static string createSQLString(string s)
        {

            if (s == null)
            {
                return "NULL";
            }

            return StringConverter.toQuotedString(s, '\'', true);
        }

        /**
         * Explicit casts are handled here. This is separate from the implicit
         * casts carried _out when inserting/updating rows.
         * SQL standard 6.12 rules for enforcement of size, precision and scale
         * are implemented here are as follows:
         *
         * For no size, precision or scale, default to convertObject(Object)
         *
         * Right truncation is allowed only for CHAR to CHAR casts
         * (CHAR is generic for all string types).
         *
         * For other casts to CHAR, right truncation is not allowed.
         *
         * For numeric conversions, scale is always converted to target first,
         * then precision is imposed. No truncation is allowed. (fredt)
         */
        public static Object convertObject(Session session, Object o, int type,
                                           int precision,
                                           int scale)
        {

            if (o == null)
            {
                return o;
            }

            if (precision == 0)
            {
                return convertObject(o, type);
            }

            bool check = true;

            switch (type)
            {

                case Types.VARCHAR_IGNORECASE:
                case Types.LONGVARCHAR:
                    type = Types.VARCHAR;
                    break;
                case Types.VARCHAR:
                case Types.CHAR:
                    if (o is String)
                    {
                        check = false;
                    }
                    else
                    {
                        o = convertObject(o, Types.VARCHAR);
                    }

                    return enforceSize(o, type, precision, scale, check);

                case Types.NUMERIC:
                case Types.DECIMAL:
                    if (!(o is Decimal))
                    {
                        o = convertObject(o, type);
                    }

                    return enforceSize(o, type, precision, scale, true);

                case Types.TIMESTAMP:
                    if (o is DateTime)
                    {
                       // long millis = session.currentDate
                       //               + ((DateTime)o);

                       o = (DateTime)o; //HsqlDateTime.getTimestamp(millis);
                    }

                    if (o is DateTime)
                    {
                        return enforceSize(o, type, precision, scale, false);
                    }
                    break;
            }

            return convertObject(o, type);
        }

        static int[] tenPower = {
        1000000000, 100000000, 10000000, 1000000, 100000, 10000, 1000
    };

        /**
         *  Check an object for type CHAR and VARCHAR and truncate/pad based on
         *  the  size
         *
         * @param  obj   object to check
         * @param  type  the object type
         * @param  size  size to enforce
         * @param check  throw if too long
         * @return       the altered object if the right type, else the object
         *      passed _in unaltered
         * @  if data too long
         */
        public static Object enforceSize(Object obj, int type, int size, int scale,
                                  bool check)
        {

            if (obj == null)
            {
                return obj;
            }

            if (size == 0 && type != Types.TIMESTAMP)
            {
                return obj;
            }

            // todo: need to handle BINARY like this as well
            switch (type)
            {

                case Types.CHAR:
                    return checkChar((String)obj, size, check);

                case Types.VARCHAR:
                case Types.VARCHAR_IGNORECASE:
                    return checkVarchar((String)obj, size, check);

                case Types.NUMERIC:
                case Types.DECIMAL:

                    Decimal dec = Convert.ToDecimal(obj);
                    dec = Math.Round(dec, scale, MidpointRounding.AwayFromZero);

                    decimal big =  (dec *  (long)Math.Pow(10, scale));
                    int        sign = big < 0 ? 1 : 0;

                    if (big.ToString("F0").Length - sign > size)
                    {
                        throw Trace.error(Trace.STRING_DATA_TRUNCATION);
                    }

                    return dec;

                case Types.TIMESTAMP:
                    if (size == 6 || size == 5 || size == 4)
                    {
                        return obj;
                    }
                    
                    DateTime ts = (DateTime)obj;
                    decimal nanos = ts.Ticks * 100;

                    int       divisor  = tenPower[size];
                    decimal       newNanos = (nanos / divisor) * divisor;
                    long newTicks = (long) newNanos / 100;

                    return new DateTime(newTicks);           

                default:
                    return obj;
            }
        }

        /**
         *  Checks the length of a VARCHAR string.
         *
         * @param s     the string to pad to truncate
         * @param len   the len to make the string
         * @param check if true, throw an exception if truncation takes place
         * @return      the string of size len
         */
        public static string checkVarchar(string s, int len,
                                   bool check)
        {

            int slen = s.Length;

            if (slen > len)
            {
                if (check)
                {
                    throw Trace.error(Trace.STRING_DATA_TRUNCATION);
                }

                return s.Substring(0, len - (0));
            }

            return s;
        }

        /**
         *  Checks and pads a CHARACTER string to len size
         *
         * @param s     the string to pad to truncate
         * @param len   the len to make the string
         * @param check if true, throw an exception if truncation takes place
         * @return      the string of size len
         */
        public static string checkChar(string s, int len,
                                bool check)
        {

            int slen = s.Length;

            if (slen == len)
            {
                return s;
            }

            if (slen > len)
            {
                if (check)
                {
                    throw Trace.error(Trace.STRING_DATA_TRUNCATION);
                }

                return s.Substring(0, len - (0));
            }
            
            char[] b = new char[len];

            Array.Copy(s.ToCharArray(), b, slen);

            for (int i = slen; i < len; i++)
            {
                b[i] = ' ';
            }

            return new String(b);
        }

        /**
         * Type narrowing from DOUBLE/DECIMAL/NUMERIC to BIGINT / INT / SMALLINT / TINYINT
         * following the SQL rules. When conversion is from a non-integral type,
         * digits to the right of the decimal point are lost.
         */

        /**
         * Converter from a numeric object to Integer. Input is checked to be
         * within range represented by Integer.
         */
        public static int convertToInt(Object o)
        {

            if (o is Decimal)
            {
                long bi = ((long)(decimal)o);

                if (bi.CompareTo(MAX_INT) > 0 || bi.CompareTo(MIN_INT) < 0)
                {
                    throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                }

                return ((int)bi);
            }

            if (o is double || o is float)
            {
                double d = Convert.ToDouble(o);

                if (Double.IsNaN(d) || d >= (double)int.MaxValue + 1
                        || d <= (double)int.MinValue - 1)
                {
                    throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                }

                return ((int)d);
            }

            throw Trace.error(Trace.INVALID_CONVERSION);
        }

        /**
         * Converter from a numeric object to Long. Input is checked to be
         * within range represented by Long.
         */
        public static long convertToLong(Object o)
        {

            if (o is Decimal)
            {
                decimal bi = (decimal)o;

                if (bi > MAX_LONG || bi < MIN_LONG)
                {
                    throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                }

                return (long)bi;
            }

            if (o is double || o is float)
            {
                double d = Convert.ToDouble(o) ;

                if (Double.IsNaN(d) || d >= (double)long.MaxValue + 1
                        || d <= (double)long.MinValue - 1)
                {
                    throw Trace.error(Trace.NUMERIC_VALUE_OUT_OF_RANGE);
                }

                return (long)d;
            }

            throw Trace.error(Trace.INVALID_CONVERSION);
        }

        /**
         * Converter from a numeric object to Double. Input is checked to be
         * within range represented by Double
         */
        public static double convertToDouble(Object o)
        {

            double val = (double) o;

          

            return (val);
        }

        // fredt@users 20020408 - patch 442993 by fredt - arithmetic expressions

        /**
         *  Arithmetic expression terms are promoted to a type that can
         *  represent the resulting values and avoid incorrect results.<p>
         *  When the result or the expression is converted to the
         *  type of the target column for storage, an exception is thrown if the
         *  resulting value cannot be stored _in the column<p>
         *  Returns a SQL type "wide" enough to represent the result of the
         *  expression.<br>
         *  A type is "wider" than the other if it can represent all its
         *  numeric values.<BR>
         *  Types narrower than INTEGER (int) are promoted to
         *  INTEGER. The order is as follows<p>
         *
         *  INTEGER, BIGINT, DOUBLE, DECIMAL<p>
         *
         *  TINYINT and SMALLINT _in any combination return INTEGER<br>
         *  INTEGER and INTEGER return BIGINT<br>
         *  BIGINT and INTEGER return NUMERIC/DECIMAL<br>
         *  BIGINT and BIGINT return NUMERIC/DECIMAL<br>
         *  DOUBLE and INTEGER return DOUBLE<br>
         *  DOUBLE and BIGINT return DOUBLE<br>
         *  NUMERIC/DECIMAL and any type returns NUMERIC/DECIMAL<br>
         *
         * @author fredt@users
         * @param  type1  java.sql.Types value for the first numeric type
         * @param  type2  java.sql.Types value for the second numeric type
         * @return        either type1 or type2 on the basis of the above order
         */
        public static int getCombinedNumberType(int type1, int type2, int expType)
        {

            int typeWidth1 = getNumTypeWidth(type1);
            int typeWidth2 = getNumTypeWidth(type2);

            if (typeWidth1 == 16 || typeWidth2 == 16)
            {
                return Types.DOUBLE;
            }

            switch (expType)
            {

                case Expression.EQUAL:
                case Expression.BIGGER:
                case Expression.BIGGER_EQUAL:
                case Expression.SMALLER_EQUAL:
                case Expression.SMALLER:
                case Expression.NOT_EQUAL:
                case Expression.ALTERNATIVE:
                case Expression.DIVIDE:
                    return (typeWidth1 > typeWidth2) ? type1
                                                     : type2;

                default:
                    int sum = typeWidth1 + typeWidth2;

                    if (sum <= 4)
                    {
                        return Types.INTEGER;
                    }

                    if (sum <= 8)
                    {
                        return Types.BIGINT;
                    }

                    return Types.NUMERIC;
            }
        }

        /**
         * @param  type java.sql.Types int for a numeric type
         * @return relative width
         */
        public static int getNumTypeWidth(int type)
        {

            switch (type)
            {

                case Types.TINYINT:
                    return 1;

                case Types.SMALLINT:
                    return 2;

                case Types.INTEGER:
                    return 4;

                case Types.BIGINT:
                    return 8;

                case Types.REAL:
                case Types.FLOAT:
                case Types.DOUBLE:
                    return 16;

                case Types.NUMERIC:
                case Types.DECIMAL:
                    return 32;

                default:
                    return 32;
            }
        }

        /**
         * returns -1, 0 , +1
         */
        public static int compareToTypeRange(Object o, int targettype)
        {

            if (!(o is Decimal) && !(o is int) && !(o is long) && !(o is short) && !(o is byte) &&
                !(o is float) && !(o is double))
            {
                return 0;
            }

            if (o is byte || o is short || o is int || o is long)
            {
                long temp = Convert.ToInt64(o);
                int min;
                int max;

                switch (targettype)
                {

                    case Types.TINYINT:
                        min = Byte.MinValue;
                        max = Byte.MaxValue;
                        break;

                    case Types.SMALLINT:
                        min = short.MinValue;
                        max = short.MaxValue;
                        break;

                    case Types.INTEGER:
                        min = int.MinValue;
                        max = int.MaxValue;
                        break;

                    case Types.BIGINT:
                    case Types.DECIMAL:
                    case Types.NUMERIC:
                    default:
                        return 0;
                }

                if (max < temp)
                {
                    return 1;
                }

                if (temp < min)
                {
                    return -1;
                }

                return 0;
            }
            else
            {
                try
                {
                    o = convertToLong(o);

                    return compareToTypeRange(o, targettype);
                }
                catch (CoreException e)
                {
                    if (e.getErrorCode() == Trace.NUMERIC_VALUE_OUT_OF_RANGE)
                    {
                        if (o is Decimal)
                        {
                            return ((Decimal)o) > 0 ? 1 : -1; 
                        }
                        else if (o is Double)
                        {
                            return ((Double)o) > 0 ? 1 : -1;
                        }
                    }
                }
            }

            return 0;
        }

        /**
         * Converts the specified hexadecimal digit <CODE>String</CODE>
         * to an equivalent array of bytes.
         *
         * @param hexstring a <CODE>String</CODE> of hexadecimal digits
         * @  if the specified string contains non-hexadecimal digits.
         * @return a byte array equivalent to the specified string of hexadecimal digits
         */
        public static byte[] hexToByteArray(string hexString)
        {

            try
            {
                return StringConverter.hexToByte(hexString);
            }
            catch (Exception )
            {
                throw Trace.error(Trace.INVALID_CHARACTER_ENCODING);
            }
        }

        /**
         * Compares a <CODE>byte[]</CODE> with another specified
         * <CODE>byte[]</CODE> for order.  Returns a negative integer, zero,
         * or a positive integer as the first object is less than, equal to, or
         * greater than the specified second <CODE>byte[]</CODE>.<p>
         *
         * @param o1 the first byte[] to be compared
         * @param o2 the second byte[] to be compared
         * @return a negative integer, zero, or a positive integer as this object
         * is less than, equal to, or greater than the specified object.
         */
        public static int CompareTo(byte[] o1, byte[] o2)
        {

            int len = o1.Length;
            int lenb = o2.Length;

            for (int i = 0; ; i++)
            {
                int a = 0;
                int b = 0;

                if (i < len)
                {
                    a = ((int)o1[i]) & 0xff;
                }
                else if (i >= lenb)
                {
                    return 0;
                }

                if (i < lenb)
                {
                    b = ((int)o2[i]) & 0xff;
                }

                if (a > b)
                {
                    return 1;
                }

                if (b > a)
                {
                    return -1;
                }
            }
        }
    }
}
